﻿namespace IBTrader.Frontend
{
    using IBTrader.Model;
    using IBTrader.Modules;
    using IBTrader.Modules.Log;
    using IBTrader.Modules.Tws;
    using Microsoft.Practices.Prism.Commands;
    using NLog;
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using MvvmValidation;

    abstract class ViewModelBase : NotifyPropertyChanged
    {
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
        private LogLevel logLevel = NLog.LogLevel.Info;
        private string errors;
        public IIB IB { get; private set; }
        public Store Store { get; private set; }
        public Logs Logs { get; private set; }
        public event EventHandler<EventArgs> Load, Unload, Connected;
        public DelegateCommand<Control> LoadedCommand { get; private set; }
        public DelegateCommand UnloadedCommand { get; private set; }
        public DelegateCommand Clear { get; private set; }
        public DelegateCommand Connect { get; private set; }
        public DelegateCommand Disconnect { get; private set; }
        public DelegateCommand<string> ChangeLogLevel { get; private set; }
        protected Control view;
        protected IEnumerable<KeyValuePair<string, object>> objects;
        protected ValidationHelper Validator = new ValidationHelper();
        protected bool IsLoaded;
        protected ViewModelBase() { }
        protected ViewModelBase(Modules.Configuration.Component component)
        {
            if ((bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(DependencyObject)).Metadata.DefaultValue) return;

            objects = Creator.Create(component, this);
            Store = Creator.Select<Store>(objects, "store");
            Logs = new Logs();
            IB = Creator.Select<IIB>(objects, "ib");
            LoadedCommand = new DelegateCommand<Control>(OnLoad);
            UnloadedCommand = new DelegateCommand(OnUnload);
            Clear = new DelegateCommand(ClearLog);
            Connect = new DelegateCommand(OnConnect, () => !IsConnected);
            Disconnect = new DelegateCommand(OnDisconnect, () => IsConnected);
            ChangeLogLevel = new DelegateCommand<string>(s => LogLevel = s);
            IB.Connected += (o, e) => 
            {
                if (IB.IsConnected())
                {
                    LogInfo(logger, "Connection", "Connected");
                    if (Connected != null) Connected(this, EventArgs.Empty);
                    IsConnected = IB.IsConnected();
                }
            };
            IB.connectionClosed += (o, e) =>
            {
                LogInfo(logger, "Connection", "Connection Closed");
                IsConnected = false;
            };
            IB.errMsg += (_, e) => IsConnected = IB.IsConnected();
        }
        protected virtual void OnLoad(Control view) 
        {
            if (IsLoaded) return;
            IsLoaded = true;

            this.view = view;
            if (Load != null) Load(this, EventArgs.Empty);
            if (IB != null && AutoConnect) IB.Connect();
        }
        protected virtual void OnUnload() 
        {
            if (Unload != null) Unload(this, EventArgs.Empty);
            if (IB != null && IB.IsConnected())
                IB.Disconnect();
        }
        protected virtual void OnConnect() { W.X(logger, () => { IB.Connect(); }); }
        protected virtual void OnDisconnect() { IB.Disconnect(); }
        public abstract bool AutoConnect { get; set; }
        protected void Update(params DelegateCommand[] commands) { commands.ToList().ForEach(c => c.RaiseCanExecuteChanged()); }
        protected virtual bool IsValid()
        {
            var result = Validator.ValidateAll();
            Errors = result.IsValid ? null : result.ToString();
            return result.IsValid;
        }
        #region Log
        private string lastMessage;
        public string LastMessage
        {
            get { return lastMessage; }
            set { lastMessage = value; Changed(() => LastMessage); }
        }
        private bool isConnected;
        public virtual bool IsConnected
        {
            get { return isConnected; }
            set
            {
                isConnected = value; 
                Changed(() => IsConnected); 
                Update(Connect, Disconnect);
            }
        }
        public string Errors
        {
            get { return errors; }
            set { errors = value; Changed(() => Errors); }
        }
        public string LogLevel
        {
            get { return logLevel.ToString(); }
            set { logLevel = NLog.LogLevel.FromString(value); Changed(() => LogLevel); }
        }
        protected virtual void ClearLog()
        {
            while (Logs.Count > 1) Logs.RemoveAt(1);
            if (Logs.Count >= 1)
            {
                Logs[0].Text ="";
            }
            LastMessage = null;
        }
        public virtual void Log(object o, LogTarget.LogArgs e)
        {
            if (e.Level < logLevel) return;
            LogMessage(e.CallSite, e.Message);
        }
        protected virtual void LogInfo(Logger logger, string name, object format = null, params object[] args)
        {
            var message = string.Format((format ?? "").ToString(), args);
            LogMessage(name, message);
            logger.Debug(name + ":" + format, args);
        }
        private void LogMessage(string name, string format = null, params object[] args)
        {
            view.Dispatcher.BeginInvoke((Action)(() =>
            {
                var message = string.Format(format, args);
                while (Logs.Count > 100) Logs.RemoveAt(1);
                if (message.Length == 1)
                {
                    Logs[0].Text += message;
                }
                else
                    Logs.Add(new Model.Log { Name = name, Text = message });
                LastMessage = message;
            }));
        }
        protected void Invoke(Action action)
        {
            if (view == null) return;
            else if (view.Dispatcher.CheckAccess()) action();
            else view.Dispatcher.Invoke(action);
        }
        #endregion
    }
}